package com.easylive.web.controller;

import com.easylive.web.annotation.GlobalInterceptor;
import com.easylive.entity.component.RedisComponent;
import com.easylive.entity.config.AppConfig;
import com.easylive.entity.constants.Constants;
import com.easylive.entity.dto.SysSettingDto;
import com.easylive.entity.dto.TokenUserInfoDto;
import com.easylive.entity.dto.UploadingFileDto;
import com.easylive.entity.dto.VideoPlayInfoDTO;
import com.easylive.entity.enums.DateTimePatternEnum;
import com.easylive.entity.enums.ResponseCodeEnum;
import com.easylive.entity.po.VideoInfo;
import com.easylive.entity.po.VideoInfoFile;
import com.easylive.entity.query.VideoInfoQuery;
import com.easylive.entity.request.category.AdminGetSourceReqVO;
import com.easylive.entity.request.file.WebFileDelUploadVideoReqVO;
import com.easylive.entity.request.file.WebFilePreUploadVideoReqVO;
import com.easylive.entity.request.file.WebFileUploadImageReqVO;
import com.easylive.entity.request.file.WebFileUploadVideoReqVO;
import com.easylive.entity.vo.ResponseVO;
import com.easylive.exception.BusinessException;
import com.easylive.mappers.VideoInfoMapper;
import com.easylive.service.VideoInfoFileService;
import com.easylive.utils.DateUtil;
import com.easylive.utils.FFmpegUtils;
import com.easylive.utils.StringTools;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotBlank;
import java.io.*;
import java.util.Date;

/**
 * @Author: yudaxian
 * @DateTime: 2024/11/5 21:38
 **/
@RestController
@RequestMapping("/file")
@Slf4j
public class FileController extends ABaseController {

    @Resource
    private AppConfig appConfig;

    @Resource
    private FFmpegUtils fFmpegUtils;

    @Resource
    private RedisComponent redisComponent;

    @Resource
    private VideoInfoFileService videoInfoFileService;

    @Resource
    private VideoInfoMapper<VideoInfo, VideoInfoQuery> videoInfoMapper;

    /**
     * 通过流返回，可以使用业务控制，比如：视频需要付费，直接在代码中判断有没有付费就行了，
     * 如果是返回的资源路径，就没法控制了，因为nginx只能返回资源，没法做业务控制
     * @param reqVO
     * @param response
     */
    @GetMapping("/getResource")
    public void getResource(AdminGetSourceReqVO reqVO, HttpServletResponse response) {

        if (!StringTools.pathIsOk(reqVO.getSourceName())) {
            throw new BusinessException(ResponseCodeEnum.CODE_600);
        }
        String fileSuffix = StringTools.getFileSuffix(reqVO.getSourceName());
        /**
         * 判断文件后缀是否合法
         */
        if (fileSuffix == null) {
            throw new BusinessException(ResponseCodeEnum.CODE_600);
        }
        response.setContentType("image/" + fileSuffix.replace(".", ""));
        /**
         * 设置浏览器缓存时间为30天
         */
        response.setHeader("Cache-Control", "max-age=2592000");
        /**
         * 读取文件并输出到浏览器
         * cover/202411/eFyuetVRfzdClgMRFOPqmGFmioMHNs.png
         */
        readFile(response, reqVO.getSourceName());
    }

    /**
     * 分片上传（预处理），返回上传的ID和分片数量，到时候分片上传时需要用到
     * @param reqVO
     * @return
     */
    @PostMapping("/preUploadVideo")
    @GlobalInterceptor(checkLogin = true)
    public ResponseVO preUploadVideo(@Validated WebFilePreUploadVideoReqVO reqVO) {
        // 拿到用户登录ID，生成DTO，存入Redis中，返回唯一ID，uploadId
        TokenUserInfoDto tokenUserInfoDto = getTokenUserInfoDto();
        String uploadId = redisComponent.savePreUploadVideoInfo(tokenUserInfoDto.getUserId(),
                reqVO.getFileName(), reqVO.getChunks());

        return getSuccessResponseVO(uploadId);
    }

    /**
     * 分片上传，根据uploadId和分片序号上传分片
     * 不分片的话，直接上传整个文件，太大了就会很慢，请求超时，所以分片，前端设置的512KB
     * @param reqVO
     * @return
     */
    @PostMapping("/uploadVideo")
    @GlobalInterceptor(checkLogin = true)
    public ResponseVO uploadVideo(@Validated WebFileUploadVideoReqVO reqVO) throws IOException {
        /**
         * 1. 取出fileDto
         * 2. 校验信息，文件大小，文件后缀
         */
        TokenUserInfoDto tokenUserInfoDto = getTokenUserInfoDto();

        UploadingFileDto uploadFileDto = redisComponent.getUploadFileDto(tokenUserInfoDto.getUserId(), reqVO.getUploadId());
        if (ObjectUtils.isEmpty(uploadFileDto)) {
            throw new BusinessException("视频不存在请重新上传");
        }

        SysSettingDto sysSettingDto = redisComponent.getSysSetting();
        if (reqVO.getChunkFile().getSize() > sysSettingDto.getVideoSize() * Constants.SIZE_1MB) {
            throw new BusinessException("视频大小超出限制");
        }

        // 判断分片，因为每次上传后都需要更新redis中的fileDto
        if ((reqVO.getChunkIndex() - 1) > uploadFileDto.getChunkIndex() || reqVO.getChunkIndex() > uploadFileDto.getChunks() - 1) {
            throw new BusinessException(ResponseCodeEnum.CODE_600);
        }

        // 获取文件夹
        String folder = appConfig.getProjectFolder() + Constants.FILE_FOLDER + Constants.FILE_TEMP + uploadFileDto.getFilePath();
        /**
         * 这里直接写入分片文件，按照分片序号命名
         */
        File targetFIle = new File(folder + "/" + reqVO.getChunkIndex());
        reqVO.getChunkFile().transferTo(targetFIle);

        // 最后更新Redis中的fileDto, 更新的属性只有分片序号和文件大小
        uploadFileDto.setChunkIndex(reqVO.getChunkIndex());
        uploadFileDto.setFileSize(uploadFileDto.getFileSize() + reqVO.getChunkFile().getSize());
        redisComponent.updateUploadFileDto(tokenUserInfoDto.getUserId(), uploadFileDto);

        return getSuccessResponseVO(null);
    }

    @PostMapping("/delUploadVideo")
    @GlobalInterceptor(checkLogin = true)
    public ResponseVO delUploadVideo(@Validated WebFileDelUploadVideoReqVO reqVO) throws IOException {

        TokenUserInfoDto tokenUserInfoDto = getTokenUserInfoDto();
        String uploadId = reqVO.getUploadId();
        UploadingFileDto uploadFileDto = redisComponent.getUploadFileDto(tokenUserInfoDto.getUserId(), uploadId);

        if (ObjectUtils.isEmpty(uploadFileDto)) {
            throw new BusinessException("视频不存在请重新上传");
        }

        // 删除临时文件
        String folder = appConfig.getProjectFolder() + Constants.FILE_FOLDER + Constants.FILE_TEMP + uploadFileDto.getFilePath();

        File file = new File(folder);
        if (file.exists()) {
            FileUtils.deleteDirectory(file);
        }

        // 删除redis中的记录
        redisComponent.delUploadFileDto(tokenUserInfoDto.getUserId(), uploadId);

        return getSuccessResponseVO(uploadId);
    }

    @PostMapping("/uploadImage")
    @GlobalInterceptor(checkLogin = true)
    public ResponseVO uploadImage(@Validated WebFileUploadImageReqVO reqVO) throws IOException {
        String day = DateUtil.format(new Date(), DateTimePatternEnum.YYYYMMDD.getPattern());

        String folder = appConfig.getProjectFolder() + Constants.FILE_FOLDER + Constants.FILE_COVER + day;
        File file = new File(folder);
        if (!file.exists()) {
            file.mkdirs();
        }

        String fileName = StringTools.getImageFileName(reqVO.getFile().getOriginalFilename());
        String filePath = folder + "/" + fileName;

        reqVO.getFile().transferTo(new File(filePath));

        if (reqVO.getCreateThumbnail()) {
            fFmpegUtils.createImageThumbnail(filePath);
        }

        return getSuccessResponseVO(Constants.FILE_COVER + day + "/" + fileName);
    }

    /**
     * 通过fileId，读取视频文件，返回m3u8文件
     * @param fileId
     */
    @Transactional(rollbackFor = Exception.class)
    @GetMapping("/videoResource/{fileId}")
    public void videoResource(@PathVariable("fileId") @NotBlank String fileId, HttpServletResponse response) {
        VideoInfoFile videoInfoFile = videoInfoFileService.getVideoInfoFileByFileId(fileId);
        String filePath = videoInfoFile.getFilePath();
        readFile(response, filePath + "/" + Constants.M3U8_NAME);

        // 在播放的时候，组装 dto 对象，同时记录是谁看了视频，方便查看历史记录
        VideoPlayInfoDTO videoPlayInfoDTO = new VideoPlayInfoDTO(videoInfoFile.getVideoId(), null, videoInfoFile.getFileIndex());
        TokenUserInfoDto tokenUserInfoDto = getTokenUserInfoFromCookie();

        // 记录用户的行为
        if (!ObjectUtils.isEmpty(tokenUserInfoDto)) {
            videoPlayInfoDTO.setUserId(tokenUserInfoDto.getUserId());
        }

        // 存放到消息队列中
        redisComponent.addVideoPlay(videoPlayInfoDTO);
    }

    /**
     * 获取到m3u8文件，再接着获取ts文件
     * @param fileId
     */
    @GetMapping("/videoResource/{fileId}/{ts}")
    public void videoResourceTs(@PathVariable("fileId") @NotBlank String fileId,
                                @PathVariable("ts") @NotBlank String ts,
                                HttpServletResponse response) {
        VideoInfoFile videoInfoFile = videoInfoFileService.getVideoInfoFileByFileId(fileId);
        String filePath = videoInfoFile.getFilePath();
        readFile(response, filePath + "/" + ts);
    }


    /**
     * 读取文件，作为流输出到浏览器
     * @param response
     * @param filePath
     */
    protected void readFile(HttpServletResponse response, String filePath) {
        File file = new File(appConfig.getProjectFolder() + Constants.FILE_FOLDER + filePath);
        if (!file.exists()) {
            return;
        }
        try (OutputStream out = response.getOutputStream();
             InputStream in = new FileInputStream(file)) {
            byte[] buffer = new byte[1024];
            int len = 0;
            while ((len = in.read(buffer)) != -1) {
                out.write(buffer, 0, len);
            }
            out.flush();
        } catch (IOException e) {
            log.error("读取文件失败", e);
        }
    }
}
